#!/usr/bin/env python
#-*- coding:utf-8 -*-

"""
    Programa encargado de ejecutar en un motor de inferencia un archivo de texto
        que contiene las definiciones de atributos, constantes, reglas y
        estimulos.
"""

import sys
import cgi
import cmd
#import codecs

#from controlDeTareas import tarea
import motor

def usage():
    """
        Mensaje de ayuda de la aplicacion.
    """
    aux = ' Motor de inferencia.\n'
    aux += ' Forma de uso: \n'
    aux += '   python motor.main.py <[MODELO] >[RESULTADOS]\n'
    aux += '\n'
    aux += '  -h, --help: Mostrar este mensaje de ayuda.\n'
    aux += '\n'
    aux += ' El interprete de comandos contiene documentación accesible'
    aux +=  'mediante el comando "ayuda".'

    return aux


class MotorDeInferencia (cmd.Cmd):#(tarea.Tarea):
    """
        Encapsula las herramientas provistas por el modulo motor, para definir
            una tarea capaz de ejecutar un motor de inferencia.
    """
    def __init__(self, nombre):
        cmd.Cmd.__init__(self)

        # El motor de inferencia.
        self.motor = motor.RedDeInferencia(nombre)
        self.motor.set_entrenamiento(True)
        self.echo = False
        self.actual = 0
        self.errores = 0

        # Atributos de cmd.
        self.prompt = ''
        self.doc_header = 'Comandos documentados (ayuda <comando>):'
        self.misc_header = 'Ayudas auxiliares:'
        self.undoc_header = 'Comandos sin documentacion:'
        
    def precmd(self, linea):
        self.actual +=1
        
        linea = linea.strip()

        if linea == 'EOF':
            linea = 'salir'

        if linea.find('#') == 0:
            linea = ''

        aux = linea.split(' ',1)
        linea = aux[0].lower()
        if len(aux) > 1:
            linea += ' '+aux[1]

        if (self.echo and len(linea)>0):
            print linea
        return linea

    def emptyline(self):
        return False

    def do_ayuda(self, argumentos):
        """
            Mostrar los comandos disponibles con 'ayuda', o la documentacion de un comando con 'ayuda comando'
            Alias de 'help'
        """
        self.do_help(argumentos)

    def do_help(self, argumentos):
        """
            Mostrar los comandos disponibles con 'help', o la ayuda de un comando con 'help comando'
        """
        return cmd.Cmd.do_help(self,argumentos)    

    def do_entrenar(self, argumentos):
        """
            Configura el motor en modo entrenamiento.
        """
        self.motor.set_entrenamiento(True)
        return False
    
    def do_producir(self, argumentos):
        """
            Configura el motor en modo produccion.
        """
        self.motor.set_entrenamiento(False)
        return False

    def do_regla(self, argumentos):
        """
            Permite agregar una regla al motor de inferencia.
        """
        try:         
            if not self.motor.definir_regla(argumentos):
                print >>sys.stderr , '['+str(self.actual) + ']: La regla no pudo ser definida.'
                self.errores += 1
        except Exception as e:
                print >>sys.stderr , '['+str(self.actual) + ']: La regla no esta bien definida.'
                print >>sys.stderr , '  Excepcion: '+str(e)
                self.errores += 1
        return False

    def do_hecho(self, argumentos):
        """
            Agrega un hecho al motor de inferencia, propagandolo por la red.
        """
        hecho = motor.Hecho.from_string(argumentos)
        if hecho is None:
            print >>sys.stderr , '['+str(self.actual) + ']: El hecho no esta bien definido.'
            self.errores += 1
        else:
            self.motor.propagar(hecho)
            
        return False

    def do_atributo(self, argumentos):
        """
            Define una cadena de caracteres que debe ser considerada como un atributo.
        """
        if not self.motor.definir_atributo(argumentos):
            print >>sys.stderr, '['+str(self.actual) + ']: El atributo "'+ argumentos.split('=')[0].strip() + " ya existe"
            self.errores += 1
        return False

    def do_constante(self, argumentos):
        """
            Define una cadena de caracteres que debe ser considerada como constante.
        """
        try:
            partes = argumentos.split('=', 1)
            nombre = partes[0].strip()
            valor = partes[1].strip()

            if not self.motor.definir_constante(nombre, valor):
                print >>sys.stderr , '['+str(self.actual) + ']: La constante ' + nombre + 'ya existe.'
                self.errores += 1
        except Exception:
            print >>sys.stderr,  '['+str(self.actual) + ']: La constante no esta bien definida.'
            self.errores += 1
            
        return False

    def do_eco(self, argumentos):
        """
            Configura el interprete de comandos para imprimir cada comando antes de resolverlo.
        """
        if argumentos == 'True':
            self.echo = True
        elif argumentos == 'False':
            self.echo = False
        else:
            self.echo = not self.echo
        if self.echo:
            print >>sys.stderr, 'Se imprimiran los comandos siguientes antes de ejecutarlos.'
        else:
            print >>sys.stderr, 'Se deja de imprimir los comandos siguientes antes de ejecutarlos.'
        return False

    def do_salir(self, argumentos):
        """
            Sale del interprete de comandos y finaliza el motor de inferencia.
        """
        print >>sys.stderr, 'Saliendo.'
        return True

    def default(self, linea):
        print >>sys.stderr, '[' + str(self.actual) + ']' \
                            + ': No se reconoce el comando: "' + linea + '"'
        self.errores += 1
        return False

    def _resumen(self):
        """
            Conjuntos utilizados por do_estado y do_informe_html.
        """
        activadores = set()
        no_activadas = list()    
        activadas = dict()
        for regla in self.motor.reglas.values():
            if len(regla.hechos) > 0:
                activadas[regla] = list()

                # Recorro todos los hechos que activaron esta regla.
                for actual in regla.hechos:
                    esnuevo = True
                    # Compruebo contra los hechos activadores encontrados
                    #  anteriormente, quedandome solo con los no equivalentes.
                    for activador in activadores:
                        if activador.equivalente(actual):
                            esnuevo = False
                            break
                    if esnuevo:
                        activadores.add(actual)
                for hecho in regla.hechos:
                    activadas[regla].append(hecho)
            else:
                no_activadas.append(regla)

        return (activadas, no_activadas, activadores)
    
    def do_estado(self, argumentos):
        """
            Genera un reporte en pantalla acerca del estado de la red de
                de inferencia. Indicando reglas activadas, no activadas, hechos
                activadores, constantes y operadores.
        """

        (activadas, no_activadas, activadores) = self._resumen()

        print 'Reglas activadas:'
        for regla, hechos in activadas.items():
            print '   '+regla.get_nombre()+':'
            for hecho in hechos:
                print '    ' + str(hecho.merge())
                
            
        if len(no_activadas) > 0:
            print 'Reglas NO activadas:'
            for regla in no_activadas:
                aux = str(regla)
                print '   '+aux
                            
        cant_r = str(len(self.motor.reglas))
        cant_a = str(len(activadores))
        aux = 'Resumen de reglas: ' + str(len(activadas))+'/'+cant_r
        aux += ' [hechos activadores: '+cant_a+']'
        print aux

#       print 'Atributos:   ' + str(len(self.motor.atributos))
#       for atr in self.motor.atributos:
#           print '   ' + str(atr)
    #   print 'Estimulos:   ' + str(len(estimulos)))

        aux = len(self.motor.constantes)
        print 'Constantes:  ' + str(aux)
#       for nombre,valor in self.motor.constantes.items():
#           if nombre != valor:
#               print '   ' + str(nombre)+':= ' + str(valor)
#           else:
#               print '   ' + str(nombre)
        aux = len(self.motor.operadores)
        print 'Operaciones: ' + str(aux)
#       for op in self.motor.operadores:
#           print '   ' + str(op)
#           for r in self.motor.operadores[op].resultados:
#               print '   '.join(map(str,r))
#           else:
#               print '      Sin hechos'

        print 'Errores en el flujo de entrada: ' + str(self.errores)
        return False

    def do_informe_html(self, argumentos):
        """
            Imprime un informe en formato HTML.
            Se puede indicar en el argumento el nombre del archivo donde
                guardar el archivo, si no se da un argumento el informe es
                impreso por pantalla.
        """

        nombre = argumentos.strip()
        archivo = sys.stdout

        if len(nombre) > 0:
            archivo = open(nombre,'w')

        (activadas, no_activadas, activadores) = self._resumen()
        
        print >>archivo, '<!DOCTYPE html>'
        print >>archivo, '<html lang="es">'        
        print >>archivo, '<head>'
        print >>archivo, ' <meta charset="utf-8"/>'
        print >>archivo, ' <title>Recomendaciones Bursatiles</title>'
        print >>archivo, ' <meta name="description" content="Informe '
        print >>archivo, 'HTML de recomendaciones bursatiles"/>'
        print >>archivo, ' <meta name="author" content="' + str(type(self))
        print >>archivo, '"/>'
        print >>archivo, '</head>'

        print >>archivo, '<body>'
        print >>archivo, '<h1>Reglas activadas</h1>'
        print >>archivo, '<ul>'
        for regla, hechos in activadas.items():
            atributos = set()
            for hecho in hechos:
                for atr in hecho.list_atributos():
                    atributos.add(atr)

            nombre = regla.get_nombre()
            ecuacion = regla.ecuacion.to_infija()
            print >>archivo, ' <li><b>'+cgi.escape(nombre) + \
                    '</b>: <em>'+cgi.escape(ecuacion)+'</em>'
            style = 'style="border:1px solid black;"'
            print >>archivo, '  <table ' + style+'>'
            print >>archivo, '   <tr >'
            for atr in atributos:
                    print >>archivo, '    <th ' + style+'>'+cgi.escape(atr.replace('_',' '))+'</th>'
#            print >>archivo, '    <th '+style+'>Valor</th>'
            print >>archivo, '   </tr>'

            for hecho in hechos:
                print >>archivo, '   <tr>'
                for atr in atributos:
                    print >>archivo, '    <td ' + style+'>'
                    try:
                        aux = cgi.escape(hecho.get_atributo(atr))
                        if atr in regla.ecuacion.get_componentes():
                            aux = '<b>'+aux+'</b>'
                    except KeyError:
                        aux = ' '
                    print >>archivo, aux
                    print >>archivo, '</td>'
#                try:
#                    aux = '  <td '+style+'><i>'+str(hecho.get_atributo('resultado')+'<i></td>'
#                    print >>archivo, aux
#                except AttributeError:
#                    pass
                print >>archivo, '  </tr>'
            print >>archivo, '  </table>'
            print >>archivo, ' </li>'              
        print >>archivo, '</ul>'              
            
        if len(no_activadas) > 0:
            print >>archivo, '<h1>Reglas NO activadas</h1>'
            print >>archivo, '<ul>'
            for regla in no_activadas:
                print >>archivo, ' <li>'+str(regla)+'</li>'
            print >>archivo, '</ul>'
                            
#       print >>archivo, 'Atributos:   ' + str(len(self.motor.atributos))
#       for atr in self.motor.atributos:
#           print >>archivo, '   ' + str(atr)
    #   print >>archivo, 'Estimulos:   ' + str(len(estimulos)))

        print >>archivo, '<h2>Constantes</h2>'
        print >>archivo, '<ul>'
        for const in self.motor.constantes.values():
            print >>archivo, ' <li>'+const.get_nombre()
            if const.get_nombre() != const.get_valor():
                print >>archivo, ' = '+ const.get_valor()
            print >>archivo, '</li>'
        print >>archivo, '</ul>'

#       for nombre,valor in self.motor.constantes.items():
#           if nombre != valor:
#               print >>archivo, '   ' + str(nombre)+':= ' + str(valor)
#           else:
#               print >>archivo, '   ' + str(nombre)

        print >>archivo, '<h2>Operaciones</h2>'
        print >>archivo, '<ul>'
        for operador in self.motor.operadores:
            print >>archivo, ' <li>'+cgi.escape(operador)+'</li>'
        print >>archivo, '</ul>'


        procesados = self.aux(self.motor.atributos.values(), 0)

        por_grado = list()
        index = dict()
        r_grado = 1

        for p in procesados.values():
            r_grado = max(p.max_grado, r_grado)
            
        for p in procesados.values():
            if isinstance(p.node, motor.Regla):
                p.max_grado = r_grado
            while len(por_grado) < p.max_grado+1:
                por_grado.append(list())
            por_grado[p.max_grado].append(p)
            index[p.nombre] = (p.max_grado, len(por_grado[p.max_grado])-1)

        maxx = 0
        maxy = 0
        for grado, items in enumerate(por_grado):
            cx = 100*grado + 15
            if grado>0:
                cx += 75
            if grado == r_grado:
                cx += 75
            cy = 15
            for i in xrange(len(items)):
                cy += 75
                back = items[i]
                back.cy = cy
                back.cx = cx
                maxx = max(maxx, cx)
                maxy = max(maxy, cy)
                items[i] = back

        circles = list()
        lines = list()
        text = list()
        for g, items in enumerate(por_grado):
            for i in items:
                nombre = i.nombre
                if isinstance(i.node, motor.Atributo):
                    fill = '"blue"'
                elif isinstance(i.node, motor.OperadorBinario):
                    nombre = i.node.operador.get_nombre()
                    fill = '"purple"'
                elif isinstance(i.node, motor.Regla):
                    fill = '"green"'

                style = 'fill='+fill+' stroke-width="3" stroke="white"'
                circles.append(' <circle cx="'+str(i.cx)+'" cy="'+str(i.cy)+'" r="10" '+style+'/>')# caption="'+i.nombre+'"/>')

                style = 'stroke='+fill+' strok-width="2"'
                text.append('<text x="'+str(i.cx-7)+'" y="'+str(i.cy-15)+'" '+style+'>'+nombre+'</text>')
                for a in i.node.get_outputs():
                    (grado, n) = index[a.get_nombre()]
                    dest = por_grado[grado][n]
                    print >>sys.stderr, (n, dest.cx, dest.cy)
                    style = 'stroke-width="2"'
                    if grado-g > 3:
                        style+= ' stroke="red"'
                    elif grado-g > 2:
                        style+= ' stroke="black"'
                    elif grado-g > 1:
                        style+= ' stroke="cyan"'
                    else:
                        style+= ' stroke="gray"'
                    lines.append(' <line x1="'+str(i.cx)+'" y1="'+str(i.cy)+'" x2="'+str(dest.cx)+'" y2="'+str(dest.cy)+'" '+style+'/>')

        print >>archivo, '<details style="border: solid black 1px">'
        print >>archivo, '<summary>Forma de la red</summary>'
        print >>archivo, '<svg width="'+str(maxx+300)+'" height="'+str(maxy+15)+'">'
        for l in lines:
            print >>archivo, l
        for c in circles:
            print >>archivo, c
        for t in text:
            print >>archivo, t
        print >>archivo, '</svg>'
        print >>archivo, '</details>'
        
        print >>archivo, '</body>'
        print >>archivo, '</html>'

        return False

    def aux(self, elems, grado):
        procesados = dict()
        for e in elems:
            nombre = e.get_nombre()
            node = None
            try:
                node = procesados[nombre]
            except KeyError:
                node = HelperGraphNode(e)

            node.inputs.add(e.get_nombre())
            node.max_grado = grado
            procesados[nombre] = node
            
            nuevos = self.aux(e.get_outputs(), grado+1)
            for n in nuevos.values():
                procesados[n.nombre] = n

        return procesados    

class HelperGraphNode(object):
    def __init__(self, node):
        self.node = node
        self.nombre = node.get_nombre()
        self.inputs = set()
        self.max_grado = 0
        self.cx = 0
        self.cy = 0

def main():
    if '--help' in sys.argv or '-h' in sys.argv:
        print usage()
        sys.exit(0)            

    red = MotorDeInferencia('inferir')
    red.cmdloop()
    sys.exit(red.errores)

if __name__ == '__main__':
    main()
